#!/usr/bin/env python
# -*- coding: utf-8 -*-

import shutil
import uuid
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import logging
import asyncio
import subprocess
import os
import re

from .work_dir_config import work_dir_config

logger = logging.getLogger(__name__)

@dataclass
class FileInfo:
    """文件信息数据类"""
    file_path: Path
    file_id: str
    file_name: str
    file_size: int
    file_type: str  # "image", "document", "file"
    mime_type: str

class BaseConverter(ABC):
    """转换器基类"""
    
    # 子类需要定义的依赖
    REQUIRED_DEPENDENCIES = {}
    OPTIONAL_DEPENDENCIES = {}
    
    def __init__(self, temp_dir: Path, file_id: Optional[str] = None):
        """
        初始化转换器
        
        Args:
            temp_dir: 临时目录（兼容旧版本）
            file_id: 文件ID，用于创建专用工作目录
        """
        # 如果提供了file_id，使用统一的工作目录配置
        if file_id:
            self.file_id = file_id
            self.temp_dir = work_dir_config.create_file_work_dir(file_id)
            self.converter_work_dir = work_dir_config.create_converter_work_dir(
                file_id, self.__class__.__name__.lower()
            )
        else:
            # 兼容旧版本，使用传入的temp_dir
            self.file_id = None
            self.temp_dir = temp_dir
            self.converter_work_dir = temp_dir
            self.temp_dir.mkdir(parents=True, exist_ok=True)
        
        # 检查依赖可用性
        self._check_dependencies()
    
    def _check_dependencies(self):
        """检查依赖是否可用"""
        self.available_dependencies = {}
        
        # 检查必需依赖
        for dep_name, dep_package in self.REQUIRED_DEPENDENCIES.items():
            try:
                __import__(dep_package)
                self.available_dependencies[dep_name] = True
            except ImportError:
                self.available_dependencies[dep_name] = False
                logger.warning(f"必需依赖 {dep_name} ({dep_package}) 不可用")
        
        # 检查可选依赖
        for dep_name, dep_package in self.OPTIONAL_DEPENDENCIES.items():
            try:
                __import__(dep_package)
                self.available_dependencies[dep_name] = True
                logger.debug(f"可选依赖 {dep_name} 可用")
            except ImportError:
                self.available_dependencies[dep_name] = False
                logger.debug(f"可选依赖 {dep_name} 不可用")
    
    def is_dependency_available(self, dependency: str) -> bool:
        """检查依赖是否可用"""
        return self.available_dependencies.get(dependency, False)
    
    def require_dependency(self, dependency: str, package_name: Optional[str] = None):
        """要求依赖必须可用，否则抛出异常"""
        if not self.is_dependency_available(dependency):
            package_name = package_name or self.OPTIONAL_DEPENDENCIES.get(dependency, dependency)
            raise ImportError(
                f"缺少必要的依赖 {dependency}。请安装: pip install {package_name}"
            )
    
    def create_file_info(self, file_path: Path, file_id: str, file_type: str) -> FileInfo:
        """创建文件信息对象"""
        try:
            file_size = file_path.stat().st_size if file_path.exists() else 0
        except (OSError, PermissionError):
            file_size = 0
        
        return FileInfo(
            file_path=file_path,
            file_id=file_id,
            file_name=file_path.name,
            file_size=file_size,
            file_type=file_type,
            mime_type=self._get_mime_type(file_path)
        )
    
    def _get_mime_type(self, file_path: Path) -> str:
        """根据文件扩展名推断MIME类型"""
        ext = file_path.suffix.lower()
        mime_mapping = {
            '.png': 'image/png',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.gif': 'image/gif',
            '.bmp': 'image/bmp',
            '.svg': 'image/svg+xml',
            '.webp': 'image/webp',
            '.tiff': 'image/tiff',
            '.pdf': 'application/pdf',
            '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            '.doc': 'application/msword',
            '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            '.ppt': 'application/vnd.ms-powerpoint',
            '.md': 'text/markdown',
            '.txt': 'text/plain'
        }
        return mime_mapping.get(ext, 'application/octet-stream')
    
    async def save_image_data(self, img_data: Any, img_path: Path) -> bool:
        """
        通用的图片数据保存方法
        
        Args:
            img_data: 图片数据（可能是PIL Image、bytes、文件对象等）
            img_path: 保存路径
            
        Returns:
            bool: 是否保存成功
        """
        try:
            # 确保目录存在
            img_path.parent.mkdir(parents=True, exist_ok=True)
            
            # 检查图片数据类型并适当处理
            if hasattr(img_data, 'save'):
                # PIL Image对象，保存为指定格式
                if not img_path.suffix:
                    img_path = img_path.with_suffix('.png')
                img_data.save(img_path)
                logger.debug(f"保存PIL图片: {img_path}")
                
            elif isinstance(img_data, bytes):
                # 字节数据，直接写入
                with open(img_path, 'wb') as f:
                    f.write(img_data)
                logger.debug(f"保存字节图片: {img_path}")
                
            else:
                # 其他类型，尝试转换
                import io
                if hasattr(img_data, 'read'):
                    # 文件类对象
                    img_bytes = img_data.read()
                    with open(img_path, 'wb') as f:
                        f.write(img_bytes)
                    logger.debug(f"保存文件流图片: {img_path}")
                else:
                    # 尝试转换为字符串再编码
                    img_bytes = str(img_data).encode('utf-8')
                    with open(img_path, 'wb') as f:
                        f.write(img_bytes)
                    logger.debug(f"保存字符串图片: {img_path}")
            
            return True
            
        except Exception as e:
            logger.warning(f"保存图片失败 ({type(img_data).__name__}): {e}")
            logger.debug(f"图片数据类型: {type(img_data)}, 路径: {img_path}")
            return False
    
    def collect_generated_files(self, output_dir: Path, file_patterns: Optional[List[str]] = None) -> List[FileInfo]:
        """
        收集生成的文件信息
        
        Args:
            output_dir: 输出目录
            file_patterns: 文件模式列表，默认包含常见的图片和文档格式
            
        Returns:
            List[FileInfo]: 文件信息列表
        """
        if file_patterns is None:
            file_patterns = ['*.md', '*.png', '*.jpg', '*.jpeg', '*.gif', '*.bmp', '*.svg']
        
        file_infos = []
        
        for pattern in file_patterns:
            for generated_file in output_dir.rglob(pattern):
                if generated_file.is_file():
                    # 根据扩展名确定文件类型
                    file_type = self._determine_file_type(generated_file)
                    file_info = self.create_file_info(generated_file, str(uuid.uuid4()), file_type)
                    file_infos.append(file_info)
        
        return file_infos
    
    def _determine_file_type(self, file_path: Path) -> str:
        """根据文件扩展名确定文件类型"""
        ext = file_path.suffix.lower()
        if ext in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg', '.webp']:
            return "image"
        elif ext in ['.md', '.txt', '.html']:
            return "document"
        else:
            return "file"
    
    def update_image_paths_in_markdown(self, markdown_content: str, artifacts_dir: Path, file_stem: str) -> str:
        """
        通用的markdown图片路径更新方法
        
        Args:
            markdown_content: markdown内容
            artifacts_dir: artifacts目录路径
            file_stem: 文件名（不含扩展名）
            
        Returns:
            str: 更新后的markdown内容
        """
        # 匹配markdown图片语法: ![alt](path)
        image_pattern = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)')
        
        def replace_image_path(match):
            alt_text = match.group(1)
            original_path = match.group(2)
            
            # 如果路径已经是相对路径且指向artifacts目录，保持不变
            if f"{file_stem}_artifacts" in original_path:
                return match.group(0)
            
            # 提取文件名
            path_obj = Path(original_path)
            filename = path_obj.name
            
            # 构建新的相对路径
            new_path = f"{file_stem}_artifacts/{filename}"
            
            return f"![{alt_text}]({new_path})"
        
        updated_content = image_pattern.sub(replace_image_path, markdown_content)
        return updated_content
    
    def fix_markdown_image_paths(self, markdown_content: str, artifacts_dir: Path, file_stem: str) -> str:
        """
        修正markdown中的图片路径，确保指向正确的artifacts目录
        
        Args:
            markdown_content: markdown内容
            artifacts_dir: artifacts目录路径
            file_stem: 文件名（不含扩展名）
            
        Returns:
            str: 修正后的markdown内容
        """
        # 获取artifacts目录中的实际文件
        actual_images = []
        if artifacts_dir.exists():
            for img_file in artifacts_dir.glob("*"):
                if img_file.suffix.lower() in ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg']:
                    actual_images.append(img_file.name)
        
        logger.debug(f"实际图片文件: {actual_images}")
        
        # 处理占位符
        if '<!-- image -->' in markdown_content:
            logger.warning("发现<!-- image -->占位符，可能没有正确导出图片")
            markdown_content = markdown_content.replace('<!-- image -->', '*[图片未能正确提取]*')
        
        # 匹配markdown图片语法: ![alt](path)
        image_pattern = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)')
        
        def fix_image_path(match):
            alt_text = match.group(1)
            original_path = match.group(2)
            
            # 如果路径已经正确指向artifacts目录，保持不变
            if f"{file_stem}_artifacts" in original_path and any(img in original_path for img in actual_images):
                return match.group(0)
            
            # 尝试从原始路径中提取文件名
            path_obj = Path(original_path)
            filename = path_obj.name
            
            # 查找匹配的实际图片文件
            matched_image = None
            for actual_img in actual_images:
                if filename == actual_img or filename in actual_img or actual_img in filename:
                    matched_image = actual_img
                    break
            
            if matched_image:
                # 构建正确的相对路径
                new_path = f"{file_stem}_artifacts/{matched_image}"
                logger.debug(f"修正图片路径: {original_path} -> {new_path}")
                return f"![{alt_text}]({new_path})"
            else:
                # 如果找不到匹配的图片，尝试使用第一个图片
                if actual_images:
                    new_path = f"{file_stem}_artifacts/{actual_images[0]}"
                    logger.debug(f"使用第一个图片: {original_path} -> {new_path}")
                    return f"![{alt_text}]({new_path})"
                else:
                    # 没有找到任何图片，保持原样
                    logger.warning(f"无法匹配图片路径: {original_path}")
                    return f"![{alt_text}]({original_path})"
        
        updated_content = image_pattern.sub(fix_image_path, markdown_content)
        
        # 统计修正的数量
        original_refs = len(re.findall(r'!\[.*?\]\([^)]+\)', markdown_content))
        updated_refs = len(re.findall(r'!\[.*?\]\([^)]+\)', updated_content))
        
        if original_refs > 0:
            logger.info(f"图片路径修正完成: 原有{original_refs}个引用，修正后{updated_refs}个引用")
        
        return updated_content
    
    async def convert_with_docling(self, file_path: Path, **kwargs) -> Tuple[str, List[FileInfo]]:
        """
        通用的docling转换方法
        
        Args:
            file_path: 文件路径
            **kwargs: 其他参数
            
        Returns:
            tuple: (markdown内容, 文件信息列表)
        """
        self.require_dependency('docling', 'docling')
        
        try:
            # 动态导入docling相关模块
            from docling.document_converter import DocumentConverter  # type: ignore
            from docling_core.types.doc import ImageRefMode  # type: ignore
            
            logger.info(f"使用docling转换文件，配置图片外链模式: {file_path.name}")
            
            # 使用转换器专用工作目录
            output_base_dir = self.converter_work_dir / "docling_output"
            output_base_dir.mkdir(exist_ok=True)
            
            # 为当前文件创建专门的artifacts目录
            file_stem = file_path.stem
            artifacts_dir = output_base_dir / f"{file_stem}_artifacts"
            artifacts_dir.mkdir(exist_ok=True)
            
            # 创建转换器（自动选择正确的后端）
            converter = DocumentConverter()
            
            logger.info(f"转换文件: {file_path}")
            logger.info(f"Artifacts输出目录: {artifacts_dir}")
            
            # 转换文档
            result = converter.convert(str(file_path))
            
            # 检查转换状态
            if not hasattr(result, 'document') or result.document is None:
                error_msg = f"docling转换失败: 未生成文档对象"
                logger.error(error_msg)
                raise RuntimeError(error_msg)
            
            # 提取markdown内容 - 使用图片外链模式
            markdown_content = ""
            try:
                # 使用save_as_markdown方法，这是推荐的方式
                temp_md_path = artifacts_dir.parent / f"{file_stem}_temp.md"
                
                # 保存markdown，使用REFERENCED模式和artifacts_dir
                result.document.save_as_markdown(
                    temp_md_path,
                    artifacts_dir=artifacts_dir,
                    image_mode=ImageRefMode.REFERENCED
                )
                
                # 读取生成的markdown内容
                with open(temp_md_path, 'r', encoding='utf-8') as f:
                    markdown_content = f.read()
                
                logger.info(f"成功导出Markdown，使用REFERENCED图片模式")
                
                # 验证markdown内容是否有效
                if not markdown_content or not markdown_content.strip():
                    error_msg = "docling生成的markdown内容为空"
                    logger.error(error_msg)
                    raise RuntimeError(error_msg)
                
                # 检查是否包含图片引用
                image_refs = re.findall(r'!\[.*?\]\([^)]+\)', markdown_content)
                if image_refs:
                    logger.info(f"找到 {len(image_refs)} 个图片引用")
                    for i, ref in enumerate(image_refs[:3], 1):
                        logger.info(f"  图片引用 {i}: {ref}")
                else:
                    logger.warning("Markdown中没有找到图片引用")
                
                # 清理临时文件
                if temp_md_path.exists():
                    temp_md_path.unlink()
                    
            except Exception as e:
                error_msg = f"导出markdown时出错: {str(e)}"
                logger.error(error_msg)
                raise RuntimeError(error_msg)
            
            # 收集文件信息
            file_infos = []
            
            # 添加主文档信息（原始文件）
            doc_file_info = self.create_file_info(file_path, str(uuid.uuid4()), "document")
            file_infos.append(doc_file_info)
            
            # 检查artifacts目录中的文件
            if artifacts_dir.exists():
                artifact_files = list(artifacts_dir.glob("*"))
                if artifact_files:
                    logger.info(f"在artifacts目录中找到 {len(artifact_files)} 个文件")
                    
                    for artifact_file in artifact_files:
                        if artifact_file.is_file():
                            # 确定文件类型
                            file_type = self._determine_file_type(artifact_file)
                            artifact_file_info = self.create_file_info(artifact_file, str(uuid.uuid4()), file_type)
                            file_infos.append(artifact_file_info)
                            
                            logger.debug(f"添加artifact文件: {artifact_file.name} (类型: {file_type})")
                else:
                    logger.warning("artifacts目录中没有找到文件")
            else:
                logger.warning("artifacts目录不存在")
            
            # 如果有artifacts文件，修正markdown中的图片路径
            if len(file_infos) > 1:  # 有artifacts文件
                logger.info("检查并修正Markdown中的图片路径")
                updated_markdown = self.fix_markdown_image_paths(
                    markdown_content, 
                    artifacts_dir, 
                    file_stem
                )
                markdown_content = updated_markdown
            
            # 保存markdown文件
            md_path = output_base_dir / f"{file_stem}.md"
            with open(md_path, 'w', encoding='utf-8') as f:
                f.write(markdown_content)
            
            # 添加生成的markdown文件信息
            md_file_info = self.create_file_info(md_path, str(uuid.uuid4()), "document")
            file_infos.append(md_file_info)
            
            logger.info(f"docling转换完成: {len(file_infos)} 个文件")
            
            # 显示一些图片引用示例
            image_refs = re.findall(r'!\[.*?\]\([^)]+\)', markdown_content)
            if image_refs:
                logger.info(f"图片引用示例: {image_refs[:3]}")
            
            return markdown_content, file_infos
            
        except ImportError as ie:
            error_msg = f"docling导入失败: {ie}，请确认已正确安装: pip install docling"
            logger.error(error_msg)
            raise ImportError(error_msg)
        except Exception as e:
            error_msg = f"docling转换失败: {str(e)}"
            logger.error(error_msg)
            raise RuntimeError(error_msg)
    
    def replace_markdown_image_links_with_obs_urls(self, markdown_content: str, obs_url_mapping: Dict[str, str]) -> str:
        """
        将markdown中的本地图片链接替换为OBS链接
        
        Args:
            markdown_content: markdown内容
            obs_url_mapping: 本地文件名到OBS URL的映射
            
        Returns:
            str: 更新后的markdown内容
        """
        # 匹配markdown图片语法: ![alt](path)
        image_pattern = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)', re.IGNORECASE)
        
        def replace_with_obs_url(match):
            alt_text = match.group(1)
            original_path = match.group(2)
            
            # 提取文件名
            path_obj = Path(original_path)
            filename = path_obj.name
            
            # 查找对应的OBS URL
            obs_url = obs_url_mapping.get(filename)
            if obs_url:
                logger.debug(f"替换图片链接: {original_path} -> {obs_url}")
                return f"![{alt_text}]({obs_url})"
            else:
                # 尝试模糊匹配
                for local_name, obs_url in obs_url_mapping.items():
                    if filename in local_name or local_name in filename:
                        logger.debug(f"模糊匹配替换图片链接: {original_path} -> {obs_url}")
                        return f"![{alt_text}]({obs_url})"
                
                # 没有找到匹配的OBS URL，保持原样
                logger.warning(f"未找到图片的OBS链接: {filename}")
                return match.group(0)
        
        updated_content = image_pattern.sub(replace_with_obs_url, markdown_content)
        
        # 统计替换的数量
        original_refs = len(re.findall(r'!\[.*?\]\([^)]+\)', markdown_content))
        obs_refs = len([url for url in re.findall(r'!\[.*?\]\(([^)]+)\)', updated_content) if 'obs' in url.lower()])
        
        if obs_refs > 0:
            logger.info(f"图片链接OBS替换完成: 总引用{original_refs}个，已替换为OBS链接{obs_refs}个")
        
        return updated_content
    
    @abstractmethod
    async def convert(self, file_path: Path, **kwargs) -> Tuple[str, List[FileInfo]]:
        """
        转换文件
        
        Args:
            file_path: 输入文件路径
            **kwargs: 转换参数
            
        Returns:
            tuple: (markdown内容, 文件信息列表)
        """
        pass
    
    @abstractmethod
    def get_available_methods(self) -> List[str]:
        """
        获取可用的转换方法列表
        
        Returns:
            List[str]: 可用的转换方法列表
        """
        pass
    
    def cleanup(self):
        """清理临时文件"""
        # 检查是否为调试模式
        debug_mode = False
        try:
            from .work_dir_config import work_dir_config
            debug_mode = getattr(work_dir_config, '_debug_mode', False)
        except:
            pass
        
        if debug_mode:
            logger.info(f"调试模式，保留临时目录: {self.temp_dir}")
            return
        
        if self.temp_dir.exists():
            try:
                shutil.rmtree(self.temp_dir)
                logger.debug(f"已清理临时目录: {self.temp_dir}")
            except Exception as e:
                logger.warning(f"清理临时目录失败: {e}") 